package tool.lexer

import scala.io.Source

trait Lexer {
    // This indicates to the Scala compiler that this trait will be composed with
    // a Compiler. This implies that the methods from the Reporter class will be
    // available at run-time, as well as the reference to the Source object.
    self: Compiler =>

    import Tokens._

	val OPERATORS: List[Char] = '&'::'|'::'('::')'::'['::']'::'{'::'}'::':'::';'::'.'::'*'::'/'::'+'::'-'::'<'::'='::'!'::Nil

    /* helper functions for character processing */
    def isLetter(c: Char) : Boolean = Character.isLetter(c)
    def isDigit(c: Char) : Boolean = Character.isDigit(c)
    def isDigitNonZero(c: Char) : Boolean = Character.isDigit && !(c == '0')
    def isUnderScore(c: Char) : Boolean = (c == '_')
	def isStringStart(c: Char): Boolean = (c == '"')
    def isValidIdentifierChar(c: Char): Boolean = isUnderScore(c) || isLetter(c) || isDigit(c)
    def isValidIdentifierStart(c: Char): Boolean = isLetter(c)
    def isValidNumericalStart(c: Char): Boolean = isDigitNonZero(c)
    def isValidNumerical(c: Char): Boolean = isDigit(c)
	def isValidOperator(c: Char): Boolean = OPERATORS.foreach(g => if(g == c) return true) false
	def isValidChar(c: Char): Boolean = if(c == '\n') false else true

	// the character "buffer"
	var current: String

	def scanNumber(): Token = {
		var c: Char = next
		do{
			if(isValidNumerical(c))
				current += c
			else{
				// put back the invalid character
				readChar = c
				return INTLITERAL(current.toInt)
			}
			c = next
		}while(c != null)

		return INTLITERAL(current.toInt)
	}

	def scanString(): Token = {
		var c: Char = next
		var scanning: Boolean = false
		do{
			if(c == '"' && scanning){
				return STRING(current)
			}else if(c == '"'){
				scanning = true
			}else if(isValidChar(c)){
				current += c
			}else // it may be necessary to put the invalid character back here
				return BAD
			c = next
		}while(c != null)

		return BAD // non-finished string at the end of the file
	}
	

	def getIdentifier(): Token = {
		current match{
			case "if" => IF
			case "println" => PRINTLN
			case "while" => WHILE
			case "else" => ELSE
			case "return" => RETURN
			case "var" => VAR
			case "def" => DEF
			case "object" => OBJECT
			case "class" => CLASS
			case "main" => MAIN
			case "true" => TRUE
			case "false" => FALSE
			case "this" => THIS
			case "new" => NEW
			case "int" => INTARRAY
			case "length" => ARRAYLENGTH
			case "Unit" => UNIT
			case "extends" => EXTENDS
			case "Int" => INT
			case "Bool" => BOOL
			case "String" =>  STRING

			case _ => ID(current)
		}
	}

	def scanIdentifier(): Token = {
		var c: Char = next
		var t: Token = null
		do{
			if(isValidIdentifierChar(c)){
				current += c
			}else{
				t = getIdentifier()
				readChar = c
				return t
			}
			c = next
		}while(c != null)

		return getIdentifier()
	}

	def scanOperator(): Token = {
		var c: Char = next
		val t: Token = null

		do{
			c match{
				case '+' => PLUS
				case '-' => MINUX
				case '*' => TIMES
				case '/' => DIV
				case '<' => LESSTHAN
				case '!' => NOT
				case ':' => COLON 
				case ';' => SEMICOLUMN
				case '(' => LPAR
				case ')' => RPAR
				case '[' => LBRACK
				case ']' => RBRACK
				case '{' => LCBRACK
				case '}' =>	RCBRACK
				case '.' => DOT
				case '=' if current.length <= 1 => current += c 
				case e =>
					readChar = e
					current match{
						case "==" => return EQUALS
						case "=" => return ASSIGN
					}
			}
			c = next
		}while(c != null)

		return ASSIGN
	}

	def next(): Char = {
		val c: Char
		if(readChar != null){
			c = readChar
			readChar = null
		}else if(source.hasNext){
			c = source.next
		}else{
			c = null
		}
		return c
	}

	var readChar: Char = null

    // You have access to the variable source defined in Compiler.scala, of the type
    // scala.io.Source in here. You need to use it as your character stream for the
    // file you are reading. You can use source.hasNext and source.next to read
    // characters, and source.pos to access the positions. Make sure all the tokens
    // you create have the proper position (you can always test that by calling an 
    // error on them and check the output).
    // 
    // Write as many helper functions as you need.

    /** Works like an iterator, and returns the next token from the input stream. */
    def nextToken: Token = {
		val t: Token

		if(!source.hasNext)
			return null

		if(readChar == null){
			readChar = source.next
		}

		if(isValidNumericalStart(readChar)){
			t = scanNumber()
		}else if(isValidOperator(readChar)){
			t = scanOperator()
		}else if(isValidIdentifierStart(readChar)){
			t = scanIdentifier()
		}else if(isStringStart(readChar)){
			t = scanString()
		}else
			t = BAD

		current = ""
		return t
    }
}
